如何实现进程代码段的分页度量

文章目录
  1. 1. 使用方法
  2. 2. 读取物理内存
  3. 3. 虚拟地址转换为物理地址
  4. 4. 分页度量进程的代码段
  5. 5. 知识点记录
  6. 6. 问题记录
  7. 7. 参考

之前通过读取/proc/pid/mem的方法读取某个进程的内存数据,mem内部是用copy_from_user实现的,是对虚拟地址进行的操作。但是在某一时刻,该进程的所有内存页不一定都已经被加载到内存。由于虚拟内存的存在,只有那页代码被访问到时(copy_from_user()会判断缺页的情况),才会产生缺页中断,将该页代码加载到内存。这种方式并不够理想,理想的方法是判断哪些数据页已加载到内存中,然后对其进行度量。

在google一番后,发现有三个小程序涉及到的知识可以完成这一任务,第一个是dram.c,用来创建字符设备,这个字符设备将物理内存虚拟为一个dev/dram文件。第二个是fileview.cpp,用来读取dram文件,从而获取物理内存页的数据。第三个是translate.c,用来将虚拟地址转换为物理地址。

这样编写一个内核模块,就可以实现对进程代码段的分页度量了。以下是三个小程序的使用方法、代码注释、内核模块。

使用方法

1
2
3
4
5
6
7
cd Access_Physical_Memory
make #编译dram.ko
insmod dram.ko #加载内核模块
mknod /dev/dram c 85 0 #创建字符设备,设备号设置为85
g++ fileview.cpp -o fileview
./fileview /dev/dram
再输入回车,可以输入物理地址

读取物理内存

  • 字符设备dram.c。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    //-------------------------------------------------------------------
    // dram.c
    //
    // This module implements a Linux character-mode device-driver
    // for the processor's installed physical memory. It utilizes
    // the kernel's 'kmap()' function, as a uniform way to provide
    // access to all the memory-zones (including the "high memory"
    // on systems with more than 896MB of installed physical ram).
    // The access here is 'read-only' because we deem it too risky
    // to the stable functioning of our system to allow every user
    // the unrestricted ability to arbitrarily modify memory-areas
    // which might contain some "critical" kernel data-structures.
    // We implement an 'llseek()' method so that users can readily
    // find out how much physical processor-memory is installed.
    //
    // NOTE: Developed and tested with Linux kernel version 2.6.10
    //
    // programmer: ALLAN CRUSE
    // written on: 30 JAN 2005
    // revised on: 28 JAN 2008 -- for Linux kernel version 2.6.22.5
    // revised on: 06 FEB 2008 -- for machines having 4GB of memory
    //-------------------------------------------------------------------

    #include <linux/module.h> // for module_init()
    #include <linux/highmem.h> // for kmap(), kunmap()
    #include <asm/uaccess.h> // for copy_to_user()

    char modname[] = "dram"; // for displaying driver's name
    int my_major = 85; // note static major assignment
    loff_t dram_size; // total bytes of system memory
    /*
    #ifdef __GNUC__
    typedef long long __kernel_loff_t;
    #endif
    #if defined(__GNUC__)
    typedef __kernel_loff_t loff_t;
    #endif
    loff_t 是一个long long类型
    */

    loff_t my_llseek( struct file *file, loff_t offset, int whence );
    ssize_t my_read( struct file *file, char *buf, size_t count, loff_t *pos );

    //指定成员赋值,cpp不支持
    //该结构体里都是函数指针,llseek用于改变文件的当前读写位置。read用于从设备获取数据。
    struct file_operations
    my_fops = {
    owner: THIS_MODULE,
    llseek: my_llseek,
    read: my_read,
    };

    static int __init dram_init( void )
    {
    printk( "<1>\nInstalling \'%s\' module ", modname );
    printk( "(major=%d)\n", my_major );

    //get_num_physpages()获取 所有物理内存减去内核所保留内存块后的剩余内存(是内存地址数,不是内存页数)。
    dram_size = (loff_t)get_num_physpages() << PAGE_SHIFT;
    // %08llX 前补0,域宽8位,大写16进制输出
    printk( "<1> ramtop=%08llX (%llu MB)\n", dram_size, dram_size >> 20 );
    //register_chrdev注册字符设备,
    return register_chrdev( my_major, modname, &my_fops );
    }

    static void __exit dram_exit( void )
    {
    //取消字符设备注册
    unregister_chrdev( my_major, modname );
    printk( "<1>Removing \'%s\' module\n", modname );
    }

    /*
    file:为进行读取信息的目标文件,
    buf:为对应放置信息的缓冲区(即用户空间内存地址);
    count:为要读取的信息长度;
    pos:为读的位置相对于文件开头的偏移,这里的pos是想读取的物理地址
    */
    ssize_t my_read( struct file *file, char *buf, size_t count, loff_t *pos )
    {
    struct page *pp;
    void *from;
    int page_number, page_indent, more;

    // we cannot read beyond the end-of-file
    //如果读取位置超出物理内存尺寸,则退出
    if ( *pos >= dram_size ) return 0;

    // determine which physical page to temporarily map
    // and how far into that page to begin reading from
    //根据物理地址计算对应页号
    page_number = *pos / PAGE_SIZE;
    //计算页内偏移
    page_indent = *pos % PAGE_SIZE;

    // map the designated physical page into kernel space
    //If kerel vesion is 2.6.32 or later, please use pfn_to_page() to get page, and include
    // asm-generic/memory_model.h
    //这里我的内核是3.16.82,所以改成if 1

    #if 1
    //根据物理页号获取mem_map数组中相应地址
    pp = pfn_to_page( page_number);
    #else
    pp = &mem_map[ page_number ];
    #endif

    //kmap在永久内核映射区,创建高端页框(物理页)到内核地址空间(线性地址)的长期映射
    from = kmap( pp ) + page_indent;

    // cannot reliably read beyond the end of this mapped page
    //每次只读取不超过一页的数据
    if ( page_indent + count > PAGE_SIZE ) count = PAGE_SIZE - page_indent;

    // now transfer count bytes from mapped page to user-supplied buffer
    /*
    unsigned long copy_to_user(void *to, const void *from, unsigned long n)
    to:目标地址(用户空间)
    from:源地址(内核空间)
    n:将要拷贝数据的字节数
    返回:成功返回0,失败返回没有拷贝成功的数据字节数
    */
    more = copy_to_user( buf, from, count );

    // ok now to discard the temporary page mapping
    //删除之前的映射
    kunmap( pp );

    // an error occurred if less than count bytes got copied
    if ( more ) return -EFAULT;

    // otherwise advance file-pointer and report number of bytes read
    //往后推进读取位置,返回读取的字节数
    *pos += count;
    return count;
    }

    /*
    重新定位文件读写偏移量
    whence有以下取值:
    SEEK_SET 偏移量设置为offset字节。
    SEEK_CUR 偏移量设置为当前位置加上offset字节。
    SEEK_END 偏移量设置为文件大小加上偏移字节大小。
    */
    loff_t my_llseek( struct file *file, loff_t offset, int whence )
    {
    loff_t newpos = -1;

    switch( whence )
    {
    case 0: newpos = offset; break; // SEEK_SET
    case 1: newpos = file->f_pos + offset; break; // SEEK_CUR
    case 2: newpos = dram_size + offset; break; // SEEK_END
    }

    if (( newpos < 0 )||( newpos > dram_size )) return -EINVAL;
    file->f_pos = newpos;
    return newpos;
    }

    MODULE_LICENSE("GPL");
    module_init( dram_init );
    module_exit( dram_exit );
  • fileview.cpp文件用来读取dev/dram字符设备,并输出内存页上的数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    //----------------------------------------------------------------
    // fileview.cpp
    //
    // This program displays the contents of a specified file
    // in hexadecimal and ascii formats (including any device
    // special files representing storage media). A user may
    // navigate the file's contents using arrow-key commands,
    // or may adjust the format of the hexadecimal display to
    // select from among five data-sizes: byte (B), word (W),
    // doubleword (D), quadword (Q) or octaword (O). It also
    // is possible to seek to a specified position within the
    // file by hitting the <ENTER>-key and then typing in the
    // desired (hexadecimal) address. Type <ESCAPE> to quit.
    此程序以十六进制和ascii格式显示指定文件的内容(包括表示存储介质的任何设备专用文件)
    用户可以使用箭头键命令浏览文件内容,也可以调整十六进制显示的格式,
    以便从五种数据大小中进行选择:字节(B)、字(W)、双字(D)、四字(Q)或八字(O)。
    也可以通过按<ENTER>键,然后键入所需的(十六进制)地址,在文件中查找到指定的位置。
    键入<ESCAPE>退出。
    // compile-and-link using: $ make fileview
    //
    // programmer: ALLAN CRUSE
    // written on: 26 OCT 2002
    // revised on: 07 JUN 2006 -- removed reliance on 'ncurses'
    //----------------------------------------------------------------

    #include <stdio.h> // for printf(), perror(), fflush()
    #include <fcntl.h> // for open()
    #include <string.h> // for strncpy()
    #include <unistd.h> // for read(), lseek64()
    #include <stdlib.h> // for exit()
    #include <termios.h> // for tcgetattr(), tcsetattr()

    #define MAXNAME 80
    #define BUFHIGH 16 //用十六进制输出内存数据时的行数为16
    #define BUFWIDE 16 //用十六进制输出内存数据时的宽度为16
    #define BUFSIZE 256
    #define ROW 6
    #define COL 2

    //键盘按键
    #define KB_SEEK 0x0000000A
    #define KB_QUIT 0x0000001B
    #define KB_BACK 0x0000007F
    #define KB_HOME 0x00315B1B
    #define KB_LNUP 0x00415B1B
    #define KB_PGUP 0x00355B1B
    #define KB_LEFT 0x00445B1B
    #define KB_RGHT 0x00435B1B
    #define KB_LNDN 0x00425B1B
    #define KB_PGDN 0x00365B1B
    #define KB_END 0x00345B1B
    #define KB_DEL 0x00335B1B


    char progname[] = "FILEVIEW";
    char filename[ MAXNAME + 1 ];
    char buffer[ BUFSIZE ];
    char outline[ 80 ];

    // ./fileview /dev/dram
    int main( int argc, char *argv[] )
    {
    // setup the filename (if supplied), else terminate
    //此时artv[1]为dev/dram
    if ( argc > 1 ) strncpy( filename, argv[1], MAXNAME );
    else { fprintf( stderr, "argument needed\n" ); exit(1); }

    // open the file for reading
    //以只读模式打开/dev/dram字符设备
    int fd = open( filename, O_RDONLY );
    if ( fd < 0 ) { perror( filename ); exit(1); }

    // obtain the filesize (if possible)
    //lseek64用于大文件内的读写位置跳转(可以设置64位的地址),返回相对于文件首的偏移量
    long long filesize = lseek64( fd, 0LL, SEEK_END );
    if ( filesize < 0LL )
    {
    fprintf( stderr, "cannot locate \'end-of-file\' \n" );
    exit(1);
    }

    long long incmin = ( 1LL << 8 );
    long long incmax = ( 1LL << 36 );
    long long posmin = 0LL;
    long long posmax = (filesize - 241LL)&~0xF;
    if ( posmax < posmin ) posmax = posmin;

    // initiate noncanonical terminal input
    struct termios tty_orig;
    //获取终端相关参数,第一个参数是fd,
    tcgetattr( STDIN_FILENO, &tty_orig );
    struct termios tty_work = tty_orig;
    //关闭终端回显和规范模式(规范模式是什么?)
    tty_work.c_lflag &= ~( ECHO | ICANON ); // | ISIG );
    tty_work.c_cc[ VMIN ] = 1;
    tty_work.c_cc[ VTIME ] = 0;
    //设置终端的相关参数
    tcsetattr( STDIN_FILENO, TCSAFLUSH, &tty_work );
    printf( "\e[H\e[J" );

    // display the legend
    int i, j, k;
    k = (77 - strlen( progname ))/2;
    //在1行k列位置打印FILEVIEW
    printf( "\e[%d;%dH %s ", 1, k, progname );
    k = (77 - strlen( filename ))/2;
    //打印/dev/dram
    printf( "\e[%d;%dH\'%s\'", 3, k, filename );
    char infomsg[ 80 ];
    sprintf( infomsg, "filesize: %llu (=0x%013llX)", filesize, filesize );
    k = (78 - strlen( infomsg ));
    printf( "\e[%d;%dH%s", 24, k, infomsg );
    fflush( stdout );

    // main loop to navigate the file
    long long pageincr = incmin;
    long long lineincr = 16LL;
    long long position = 0LL;
    long long location = 0LL;
    int format = 1;
    int done = 0;
    while ( !done ){
    // erase prior buffer contents
    //清除缓冲区内容,此缓冲区用来临时储存物理内存数据
    for (j = 0; j < BUFSIZE; j++) buffer[ j ] = ~0;

    // restore 'pageincr' to prescribed bounds
    if ( pageincr == 0LL ) pageincr = incmax;
    else if ( pageincr < incmin ) pageincr = incmin;
    else if ( pageincr > incmax ) pageincr = incmax;

    // get current location of file-pointer position
    //将读写位置设置为0,并获取当前读写指针的位置
    location = lseek64( fd, position, SEEK_SET );

    // try to fill 'buffer[]' with data from the file
    char *where = buffer;
    int to_read = BUFSIZE;

    //读取物理内存数据到buffer数组中
    while ( to_read > 0 ){
    int nbytes = read( fd, where, to_read );
    if ( nbytes <= 0 ) break;
    to_read -= nbytes;
    where += nbytes;
    }
    int datalen = BUFSIZE - to_read;

    // display the data just read into the 'buffer[]' array
    unsigned char *bp;
    unsigned short *wp;
    unsigned int *dp;
    unsigned long long *qp;
    for (i = 0; i < BUFHIGH; i++){
    int linelen;

    // draw the line-location (13-digit hexadecimal)
    //第一列打印地址到outline
    linelen = sprintf( outline, "%013llX ", location );

    // draw the line in the selected hexadecimal format
    switch ( format ){
    //以字节为单位读取buffer,然后用大写16进制输出到outline。
    case 1: // 'byte' format
    bp = (unsigned char*)&buffer[ i*BUFWIDE ];
    for (j = 0; j < BUFWIDE; j++)
    linelen += sprintf( outline+linelen,
    "%02X ", bp[j] );
    break;

    case 2: // 'word' format
    wp = (unsigned short*)&buffer[ i*BUFWIDE ];
    for (j = 0; j < BUFWIDE/2; j++)
    linelen += sprintf( outline+linelen,
    " %04X ", wp[j] );
    break;

    case 4: // 'dword' format
    dp = (unsigned int*)&buffer[ i*BUFWIDE ];
    for (j = 0; j < BUFWIDE/4; j++)
    linelen += sprintf( outline+linelen,
    " %08X ", dp[j] );
    break;

    case 8: // 'qword' format
    qp = (unsigned long long*)&buffer[ i*BUFWIDE ];
    for (j = 0; j < BUFWIDE/8; j++)
    linelen += sprintf( outline+linelen,
    " %016llX ", qp[j] );
    break;

    case 16: // 'octaword'
    qp = (unsigned long long*)&buffer[ i*BUFWIDE ];
    linelen += sprintf( outline+linelen, " " );
    linelen += sprintf( outline+linelen,
    " %016llX%016llX ", qp[1], qp[0] );
    linelen += sprintf( outline+linelen, " " );
    break;
    }

    // draw the line in ascii format
    //以ascii格式输出数据到outline
    for (j = 0; j < BUFWIDE; j++){
    char ch = buffer[ i*BUFWIDE + j ];
    if (( ch < 0x20 )||( ch > 0x7E )) ch = '.';
    linelen += sprintf( outline+linelen, "%c", ch);
    }

    // transfer this output-line to the screen
    //打印outline
    printf( "\e[%d;%dH%s", ROW+i, COL, outline );

    // advance 'location' for the next output-line
    location += BUFWIDE;
    }
    printf( "\e[%d;%dH", 23, COL );
    fflush( stdout );

    // await keypress
    long long inch = 0LL;
    read( STDIN_FILENO, &inch, sizeof( inch ) );
    printf( "\e[%d;%dH%60s", 23, COL, " " );

    // interpret navigation or formatting command
    //读取输入的字符
    inch &= 0x00FFFFFFLL;
    switch ( inch ){
    // move to the file's beginning/ending
    //移到文件首
    case 'H': case 'h':
    case KB_HOME: position = posmin; break;
    case 'E': case 'e':
    case KB_END: position = posmax; break;

    // move forward/backward by one line
    case KB_LNDN: position += BUFWIDE; break;
    case KB_LNUP: position -= BUFWIDE; break;

    // move forward/packward by one page
    case KB_PGDN: position += pageincr; break;
    case KB_PGUP: position -= pageincr; break;

    // increase/decrease the page-size increment
    case KB_RGHT: pageincr >>= 4; break;
    case KB_LEFT: pageincr <<= 4; break;

    // reset the hexadecimal output-format
    case 'B': case 'b': format = 1; break;
    case 'W': case 'w': format = 2; break;
    case 'D': case 'd': format = 4; break;
    case 'Q': case 'q': format = 8; break;
    case 'O': case 'o': format = 16; break;

    // seek to a user-specified file-position
    case KB_SEEK:
    printf( "\e[%d;%dHAddress: ", 23, COL );
    fflush( stdout );
    {
    char inbuf[ 16 ] = {0};
    //tcsetattr( STDIN_FILENO, TCSANOW, &tty_orig );
    int i = 0;
    while ( i < 15 ){
    long long ch = 0;
    read( STDIN_FILENO, &ch, sizeof( ch ) );
    ch &= 0xFFFFFF;
    if ( ch == '\n' ) break;
    if ( ch == KB_QUIT ) { inbuf[0] = 0; break; }
    if ( ch == KB_LEFT ) ch = KB_BACK;
    if ( ch == KB_DEL ) ch = KB_BACK;
    if (( ch == KB_BACK )&&( i > 0 ))
    {
    inbuf[--i] = 0;
    printf( "\b \b" );
    fflush( stdout );
    }
    if (( ch < 0x20 )||( ch > 0x7E )) continue;
    inbuf[ i++ ] = ch;
    printf( "%c", ch );
    fflush( stdout );
    }
    printf( "\e[%d;%dH%70s", 23, COL, " " );
    fflush( stdout );
    position = strtoull( inbuf, NULL, 16 );
    position &= ~0xFLL; // paragraph align
    }
    break;

    // program termination
    case KB_QUIT: done = 1; break;

    default:
    printf( "\e[%d;%dHHit <ESC> to quit", 23, 2 );
    }
    fflush( stdout );

    // insure that 'position' remains within bounds
    if ( position < posmin ) position = posmin;
    if ( position > posmax ) position = posmax;
    }

    // restore canonical terminal behavior
    //复原终端的各项参数
    tcsetattr( STDIN_FILENO, TCSAFLUSH, &tty_orig );
    printf( "\e[%d;%dH\e[0J\n", 23, 0 );
    }
  • Makefile文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #Makefile
    ifneq ($(KERNELRELEASE),)
    obj-m := dram.o

    else
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    default:
    $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
    rm -r -f .tmp_versions *.mod.c .*.cmd *.o *.symvers

    endif

虚拟地址转换为物理地址

由于虚拟内存的存在,一个二进制文件不是整个代码段加载到内存的。一个进程的内存页是否加载到物理内存,系统是有记录的。/proc/$pid/pagemap文件就记录了pid进程的虚拟地址和物理地址的映射情况。

  • translate.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <assert.h>
    #include <errno.h>
    #include <stdint.h>
    #include <string.h>

    #define PAGEMAP_ENTRY 8
    #define GET_BIT(X,Y) (X & ((uint64_t)1<<Y)) >> Y //返回位数组中指定位的值,X:位数组,Y:位置
    #define GET_PFN(X) X & 0x7FFFFFFFFFFFFF //获取物理页帧号

    const int __endian_bit = 1;
    #define is_bigendian() ( (*(char*)&__endian_bit) == 0 )

    int i, c, pid, status;
    unsigned long virt_addr;
    uint64_t read_val, file_offset, page_size;
    char path_buf [0x100] = {};
    FILE * f;
    char *end;

    int read_pagemap(char * path_buf, unsigned long virt_addr);

    int main(int argc, char ** argv){
    if(argc!=3){
    printf("Argument number is not correct!\n pagemap PID VIRTUAL_ADDRESS\n");
    return -1;
    }
    if(!memcmp(argv[1],"self",sizeof("self"))){
    sprintf(path_buf, "/proc/self/pagemap");
    pid = -1;
    }
    else{
    /*从字符串中解析整数
    argv[1]中为字符串
    end是一个字符指针,函数解析完long整数后,会将end指向整数之后的第一个字符。end如果为null,则不设置。
    10表示按照10进制解析数据,如果是0表示按照其本身进制标记来解析。
    */
    pid = strtol(argv[1],&end, 10);
    if (end == argv[1] || *end != '\0' || pid<=0){
    printf("PID must be a positive number or 'self'\n");
    return -1;
    }
    }
    //strtoll: Convert string to long long integer
    virt_addr = strtoll(argv[2], NULL, 16);
    if(pid!=-1)
    sprintf(path_buf, "/proc/%u/pagemap", pid);

    //获得一页内存大小
    page_size = getpagesize();
    read_pagemap(path_buf, virt_addr);
    return 0;
    }
    /*
    path_buf: /proc/pid/pagemap
    virt_addr:虚拟地址
    */
    int read_pagemap(char * path_buf, unsigned long virt_addr){
    printf("Big endian? %d\n", is_bigendian());
    //文件访问模式 r表示读,b表示以二进制读取
    f = fopen(path_buf, "rb");
    if(!f){
    printf("Error! Cannot open %s\n", path_buf);
    return -1;
    }

    //Shifting by virt-addr-offset number of bytes
    //and multiplying by the size of an address (the size of an entry in pagemap file)
    //pagemap中有一个个的页映射实体,每一个实体占8个字节。即64位,每位的作用见下一节的说明。
    file_offset = virt_addr / page_size * PAGEMAP_ENTRY;
    printf("Vaddr: 0x%lx, Page_size: %lld, Entry_size: %d\n", virt_addr, page_size, PAGEMAP_ENTRY);
    printf("Reading %s at 0x%llx\n", path_buf, (unsigned long long) file_offset);
    //设置文件指针f的偏移,偏移值以SEEK_SET为基准,偏移file_offset个字节。如果成功,返回0.否则返回非0值并设置error
    status = fseek(f, file_offset, SEEK_SET);
    if(status){
    perror("Failed to do fseek!");
    return -1;
    }
    errno = 0;
    read_val = 0;
    unsigned char c_buf[PAGEMAP_ENTRY];
    //以字节为单位读取PAGEMAP_ENTRY到c_buf中
    for(i=0; i < PAGEMAP_ENTRY; i++){
    c = getc(f);
    if(c==EOF){
    printf("\nReached end of the file\n");
    return 0;
    }
    if(is_bigendian())
    c_buf[i] = c;
    else
    c_buf[PAGEMAP_ENTRY - i - 1] = c;
    printf("[%d]0x%x ", i, c);
    }
    //将c_buf的数据转存到uint64_t型变量read_val中
    for(i=0; i < PAGEMAP_ENTRY; i++){
    //printf("%d ",c_buf[i]);
    read_val = (read_val << 8) + c_buf[i];
    }
    printf("\n");
    printf("Result: 0x%llx\n", (unsigned long long) read_val);
    //第64位为1表示该虚拟地址已经分配了相应的物理页,然后输出了物理地址
    if(GET_BIT(read_val, 63)) {
    uint64_t pfn = GET_PFN(read_val);
    printf("PFN: 0x%llx (0x%llx)\n", pfn, pfn * page_size + virt_addr % page_size);
    } else
    printf("Page not present\n");
    if(GET_BIT(read_val, 62))
    printf("Page swapped\n");
    fclose(f);
    return 0;
    }

分页度量进程的代码段

基于以上几个知识点,可以实现在某一时刻分页度量当时已被加载到物理内存的数据。
内核模块代码:https://github.com/TWS-YIFEI/Dynamic_measurement_of_process_integrity
该模块实现了度量函数,度量的动作可以通过截获系统调用来触发。

知识点记录

  • page_to_pfn

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define page_to_pfn(page) (((page) - mem_map) + PHYS_PFN_OFFSET)
    page是mem_map_t类型的指针。(mem_map结构体数组用来表示所有物理页)
    pfn(page frame number),是物理页号 (还是物理地址?为什么这里要加上PHYS_PFN_OFFSET,page-mem_map不就是页号了吗)
    page-mem_map表示该页在mem_map数组中的偏移个数,就像:
    int a[100];
    a[10]-a; //a[10]就好比page,a就好比mem_map,只不过mem_map是一个结构体数组

    #define pfn_to_page(pfn) ((mem_map + (pfn)) - PHYS_PFN_OFFSET)
    变换一种形式可以更容易理解:(mem_map + (pfn - PHYS_PFN_OFFSET))
  • 判断存储方式是大端还是小端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    1.利用联合体
    union{
    short s; //占两个字节
    char c[sizeof(short)];
    }un;

    un.s = 0x0102;
    if (sizeof(short) == 2) {
    //un.c[0]的地址为低位,如果存储的数据为高位数据,则为大端
    if (un.c[0] == 1 && un.c[1] == 2)
    printf("big-endian\n");
    else if (un.c[0] == 2 && un.c[1] == 1)
    printf("little-endian\n");
    else
    printf("unknown\n");
    } else
    printf("sizeof(short) = %d\n", sizeof(short));

    2.使用强制类型转换
    int is_little_endian(void){
    unsigned short flag=0x4321;
    if (*(unsigned char*)&flag==0x21)
    return 1;
    else
    return 0;
    }
    3.强制类型转换
    const int __endian_bit = 1;
    #define is_bigendian() ((*(char*)&__endian_bit)==0)
  • pagemap中每一个实体的结构

    1
    2
    3
    4
    5
    6
    7
    Bits 0-54: page frame number(PFN) if present
    Bits 0-4: swap type if swapped
    Bits 5-54: swap offset if swapped
    Bits 55-60:page shift
    Bit 61: reserved ofr future use
    Bit 62: page swapped
    Bit 63: page present
  • 虚拟内存&物理内存

问题记录

  • 在编译执行make编译dram模块时遇到下列信息,可以尝试这个方法:https://blog.csdn.net/u012343297/article/details/79141878
    1
    2
    3
    4
    [root@localhost Access_Physical_Memory]# make
    make -C /lib/modules/3.10.0-957.el7.x86_64/build SUBDIRS=/root/Access_Physical_Memory modules
    make: *** /lib/modules/3.10.0-957.el7.x86_64/build: No such file or directory. Stop.
    make: *** [default] Error 2

参考